package com.owg.jigsaw.slots;

import java.util.Queue;

import com.owg.jigsaw.BlockDropAction;
import com.owg.jigsaw.WBlock;
import com.owg.jigsaw.WCell;
import com.owg.jigsaw.WContainer;
import com.owg.jigsaw.WRoot;
import com.owg.jigsaw.WSingleFieldContainer;
import com.owg.jigsaw.hierarchy.TypeNode;
import com.owg.jigsaw.javafx.FXSlot;
import com.owg.jigsaw.javafx.FXUtil;

import javafx.geometry.Point2D;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import owg.util.data.PField;
import owg.util.data.PFieldElement;
import owg.util.data.PFieldImmutable;
import owg.util.data.ReferenceSet;
import owg.util.data.Referencer;
import owg.util.data.property.Property;

public abstract class Slot implements PFieldElement, WSingleFieldContainer<WBlock>
{	
	/**The slot's type. 
	 * Changing this property may cause connected blocks to be ejected.*/
	public final Property<TypeNode> slotType = new Property<>(TypeNode.class, null);
	/**The slot's background color. By default, this is null, indicating no background color.*/
	public final Property<Color> bgColor = new Property<Color>(Color.class, null);
	
	public final boolean isMultiSlot;
	private Boolean isHorizontal = null;
	private WCell cell = null;
	private FXSlot ui;
	
	protected final Referencer referencer = new ReferenceSet();

	public Slot(TypeNode slotType, boolean isMultiSlot)
	{
		this.isMultiSlot = isMultiSlot;
		this.slotType.set(slotType);
		//Eject blocks from slot if their type is no longer compatible
		this.slotType.addListener(referencer, (param, oldVal, newVal) ->
		{
			WBlock[] blocks = getChildren().toArray();
			for(WBlock b : blocks)
			{
				if(getChildren().contains(b) && accepts(b) != BlockDropAction.ACCEPT)
					b.eject();
			}
		});
	}
	
	@Override
	public void addNotify(PFieldImmutable<?> newField)
	{
		assert newField.getOwner() instanceof WCell : getClass().getSimpleName()+" can only be added to "+WCell.class.getSimpleName()+"!";
		cell = (WCell)newField.getOwner();
		isHorizontal = cell.eastSlots == newField;
	}
	/**Returns whether this slot is on the east side of a cell.
	 * If false, it is on the south side.*/
	public boolean isHorizontal()
	{
		if(isHorizontal == null)
			throw new IllegalStateException(this+" has not been added to a cell!");
		return isHorizontal;
	}

	@Override
	public void pFieldChange(PFieldImmutable<? extends WBlock> field, WBlock newChild, int index)
	{
		if(newChild != null)
		{
			TypeNode other = newChild.tabType().get();
			if(!slotType.get().equals(other) && !slotType.get().isParentOf(other))
			{
				throw new IllegalArgumentException("The block "+newChild+" cannot be added to this slot because "
					+ "the slot's type("+slotType.get()+") is not assignable from the block's type("+other+")");
			}
		}
	}
	
	@Override
	public WContainer<?> getParent()
	{
		return cell;
	}
	@Override
	public WRoot getRoot()
	{
		if(cell == null)
			throw new IllegalStateException(this+" has not been added to a cell!");
		return cell.getRoot();
	}
	@Override
	public Pane getUI()
	{
		if(ui == null)
			ui = new FXSlot(this, cell.block.color(), false);
		return ui;
	}
	public WBlock getBlock()
	{
		return cell.block;
	}

	public void putTargetSlots(Queue<Slot> sq)
	{
		sq.offer(this);
		for(WBlock b : getChildren())
		{
			b.putTargetSlots(sq);
		}
	}
	@Override
	public BlockDropAction accepts(WBlock dragBlock)
	{
		return dragBlock.tabType().get().equals(slotType.get()) || dragBlock.tabType().get().isChildOf(slotType.get())?BlockDropAction.ACCEPT:BlockDropAction.CANCEL;
	}
	
	@Override
	public WBlock getBlockAt(Point2D localPoint)
	{
		PField<WBlock> children = getChildren();
		for(int i = children.size()-1; i >= 0; i--)
		{
			WBlock b = children.get(i);
			Pane blkUI = b.getUI();
			Point2D slotPoint = FXUtil.fromTo(this.ui, blkUI, localPoint);
			WBlock blk = b.getBlockAt(slotPoint);
			
			if(blk != null)
				return blk;
		}
		return null;
	}
	
	@Override
	public String toString()
	{
		return getClass().getSimpleName()+" on "+getBlock();
	}

	/**Called when the parent block will no longer be available to the user, typically when moved to the trash can.*/
	public void dispose()
	{
		for(WBlock b : getChildren())
			b.dispose();
	}
}
